package com.swallow.auth.infrastructure.acl.oauth2.config.authserver;

import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.swallow.auth.infrastructure.acl.oauth2.constants.SecurityConstants;
import com.swallow.auth.infrastructure.acl.oauth2.ext.ExtJdbcOAuth2AuthorizationService;
import com.swallow.auth.infrastructure.acl.oauth2.handler.ErrorHandler;
import com.swallow.auth.infrastructure.acl.oauth2.handler.LoginTargetAuthenticationEntryPoint;
import com.swallow.auth.infrastructure.acl.oauth2.handler.SuccessHandler;
import com.swallow.auth.infrastructure.acl.oauth2.serializer.AccountMixin;
import com.swallow.auth.infrastructure.acl.oauth2.support.sms.SmsGrantAuthenticationConverter;
import com.swallow.auth.infrastructure.acl.oauth2.support.sms.SmsGrantAuthenticationProvider;
import com.swallow.auth.infrastructure.acl.oauth2.support.username.UPwdGrantAuthenticationConverter;
import com.swallow.auth.infrastructure.acl.oauth2.support.username.UPwdGrantAuthenticationProvider;
import com.swallow.auth.infrastructure.acl.oauth2.utils.SecurityUtils;
import com.swallow.auth.infrastructure.mysql.po.AccountPO;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;

import java.util.List;

/**
 * @author: yangjie.deng@resico.cn
 * @since: 2024-05-14 11:01:40
 * @version: v1.0.0
 * @describe:
 */
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(jsr250Enabled = true, securedEnabled = true)
public class AuthorizationConfig {

    @Resource private AuthServerProperties properties;

    /**
     * 配置端点的过滤器链
     *
     * @param http spring security核心配置类
     * @return 过滤器链
     * @throws Exception 抛出
     */
    @Bean
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        // 配置默认的设置，忽略认证端点的csrf校验
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

        // 添加自定义短信
        SmsGrantAuthenticationProvider smsProvider = new SmsGrantAuthenticationProvider();
        // 添加用户名密码登录
        UPwdGrantAuthenticationProvider pwdProvider = new UPwdGrantAuthenticationProvider();

        // 开启OpenID Connect 1.0协议相关端点
        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(Customizer.withDefaults());

        OAuth2AuthorizationServerConfigurer configurer = http.getConfigurer(OAuth2AuthorizationServerConfigurer.class);

        if (ObjectUtils.isNotEmpty(configurer)) {
            // 添加自定义grant_type
            configurer.authorizationServerMetadataEndpoint(metadata -> metadata.authorizationServerMetadataCustomizer(customer ->
                            customer.grantTypes(list -> list.addAll(List.of(SecurityConstants.GRANT_TYPE_USER_PWD_CODE, SecurityConstants.GRANT_TYPE_SMS_CODE)))));
        }

        // 关闭跨域、关闭csrf
        http.cors(AbstractHttpConfigurer::disable);
        http.csrf(AbstractHttpConfigurer::disable);

        // 当未登录时访问认证端点时重定向至login页面
        http.exceptionHandling((exceptions) -> exceptions.defaultAuthenticationEntryPointFor(
                            new LoginTargetAuthenticationEntryPoint(properties.getLoginUrl()),
                            new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
                    )
            )
            // 处理使用access token访问用户信息端点和客户端注册端点
            .oauth2ResourceServer((resourceServer) -> resourceServer.jwt(Customizer.withDefaults()));

        // 添加转换器以及处理器
        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
            .tokenEndpoint(token -> token.errorResponseHandler(ErrorHandler::exceptionHandler)
                    .accessTokenResponseHandler(new SuccessHandler())
                    .accessTokenRequestConverters(cvtList -> {
                        cvtList.add(new SmsGrantAuthenticationConverter());
                        cvtList.add(new UPwdGrantAuthenticationConverter());
                    })
                    .authenticationProviders(pvdList ->  pvdList.addAll(List.of(smsProvider, pwdProvider)))
            );

        DefaultSecurityFilterChain filterChain = http.build();

        // 自定义provider初始化属性值
        SecurityUtils.initParamByProvider(http, smsProvider, pwdProvider);

        return filterChain;
    }


    /**
     * 配置认证相关的过滤器链
     *
     * @param http spring security核心配置类
     * @return 过滤器链
     * @throws Exception 抛出
     */
    @Bean
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {

        http.authorizeHttpRequests((authorize) -> authorize
                        // 放行静态资源
                        .requestMatchers(properties.getWhitelist().toArray(String[]::new)).permitAll()
                        .anyRequest().authenticated()
                )
                // 指定登录页面
                .formLogin(formLogin ->
                        formLogin.loginPage(properties.getLoginUrl())
                                 .successHandler(new SuccessHandler())
                                 .failureHandler(ErrorHandler::exceptionHandler)
                );

        http.cors(AbstractHttpConfigurer::disable);
        http.csrf(AbstractHttpConfigurer::disable);

        // 当未登录时访问认证端点时重定向至login页面
        http.exceptionHandling(exceptions -> exceptions.defaultAuthenticationEntryPointFor(
                        new LoginTargetAuthenticationEntryPoint(properties.getLoginUrl()),
                        new MediaTypeRequestMatcher(MediaType.TEXT_HTML))
             )
             // 添加BearerTokenAuthenticationFilter，将认证服务当做一个资源服务，解析请求头中的token
            .oauth2ResourceServer((resourceServer) -> resourceServer.jwt(Customizer.withDefaults())
                                                                    .accessDeniedHandler(ErrorHandler::exceptionHandler)
                                                                    .authenticationEntryPoint(ErrorHandler::exceptionHandler));

        return http.build();
    }

    /**
     * 配置密码解析器，使用BCrypt的方式对密码进行加密和验证
     *
     * @return BCryptPasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }



    /**
     * 配置基于db的oauth2的授权管理服务
     *
     * @param jdbcTemplate               db数据源信息
     * @param registeredClientRepository 上边注入的客户端repository
     * @return JdbcOAuth2AuthorizationService
     */
    @Bean
    public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        // 基于db的oauth2认证服务
        JdbcOAuth2AuthorizationService authorizationService = new ExtJdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);

        JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper authorizationRowMapper =
                new JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper(registeredClientRepository);

        authorizationRowMapper.setLobHandler(new DefaultLobHandler());

        ObjectMapper objectMapper = new ObjectMapper();
        ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader();
        List<Module> moduleList = SecurityJackson2Modules.getModules(classLoader);
        objectMapper.registerModules(moduleList);
        objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());

        //放入自定义的UserDetails实现类, 默认序列化反序列化会报错
        objectMapper.addMixIn(AccountPO.class, AccountMixin.class);
        authorizationRowMapper.setObjectMapper(objectMapper);

        authorizationService.setAuthorizationRowMapper(authorizationRowMapper);

        return authorizationService;
    }


    /**
     * 配置基于db的授权确认管理服务
     *
     * @param jdbcTemplate               db数据源信息
     * @param registeredClientRepository 客户端repository
     * @return JdbcOAuth2AuthorizationConsentService
     */
    @Bean
    public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        // 基于db的授权确认管理服务，还有一个基于内存的服务实现InMemoryOAuth2AuthorizationConsentService
        return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
    }


    /**
     * 添加认证服务器配置，设置jwt签发者、默认端点请求地址等
     *
     * @return AuthorizationServerSettings
     */
    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder().build();
    }

}
